第七部分(一) 动态渲染页面爬取(Selenium的使用) 您所在的位置:网站首页 python 渲染网页 源代码 获取 第七部分(一) 动态渲染页面爬取(Selenium的使用)

第七部分(一) 动态渲染页面爬取(Selenium的使用)

2024-01-22 02:59| 来源: 网络整理| 查看: 265

Ajax分析和抓取方式,是JavaScript动态渲染页面的一种情形,可使用 requests 或 urllib 爬取数据。JavaScript动态渲染的页面不是只有Ajax一种,比如中国青年网 http://news.youth.cn/gn/ 的分页部分由JavaScript生成的,不是原始的HTML代码,但是不包含Ajax请求。又比如ECharts的官方实例 http://echarts.baidu.com/demo.html#bar-negative ,其图形都是经过JavaScript计算后生成的。另外的淘宝页面,有Ajax获取的数据,但是Ajax接口含有很多加密参数,不容易找出规律,很难直接分析Ajax来获取。

这些问题可以通过使用模拟浏览器运行的方式来实现,这样在浏览器中看到什么样,抓取的源码就是什么样,也就是可见即可爬。不用管网页内部的JavaScript用的什么算法渲染页面,也不用管网页后台的Ajax接口到底有哪些参数。

Python有许多模拟浏览器运行的库,如Selenium、Splash、PyV8、Ghost等。下面了解下Selenium和Splash的用法,以应对动态渲染的页面。

一、 Selenium的使用

Selenium是自动化测试工具,它可以驱动浏览器执行特定的动作,如点击、下拉等操作,同时还可以获取浏览器当前呈现的页面的源代码,做到可见即可爬。对于JavaScript动态渲染的页面,这种抓取方法非常有效。

下面以Chrome为例说明Selenium的用法。首先要正确安装Chrome浏览器并配置好ChromeDriver。还要安装好Python的Selenium库。

1、 开始使用首先看下Selenium大致有哪些功能。例如下面代码所示:

from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.common.keys import Keysfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.support.wait import WebDriverWaitbrowser = webdriver.Chrome()try: browser.get('https://www.baidu.com') input = browser.find_element_by_id('kw') input.send_keys('Python') input.send_keys(Keys.ENTER) wait = WebDriverWait(browser, 10) wait.until(EC.presence_of_element_located((By.ID, 'content_left'))) print(browser.current_url) print(browser.get_cookies()) print(browser.page_source)finally: browser.close()运行这段代码后会自动弹出一个Chrome浏览器,浏览器自动跳转到百度首页,然后在搜索框中输入Python,接着跳转到搜索结果页。搜索结果加载出来后,控制台分别会输出当前的URL、当前的Cookies和网页源代码。Cookies以字典列表形式输出。

这就是使用Selenium驱动浏览器加载网页拿到JavaScript渲染的结果,不必担心是什么加密系统。

2、 声明浏览器对象Selenium支持的浏览器非常多,如Chrome、Firefox、Edge等,还有Android、BlackBerry等手机端浏览器。还支持无界面浏览器PhangtomJS。可用下面这些方式初始这些浏览器对象:

from selenium import webdriverbrowser = webdriver.Chrome()browser = webdriver.Firefox()browser = webdriver.Edge()browser = webdriver.PhantomJS()browser = webdriver.Safari()

这就是在初始化浏览对象并将其赋值为browser对象。接下来可以调用browser对象,让其执行各个动作以模拟浏览器操作。

3、 访问页面使用前面创建的浏览器对象的 get() 方法,参数是要访问的链接URL。例如访问淘宝首页并输出源代码,示例如下:

from selenium import webdriver

chrome_options = webdriver.ChromeOptions()

chrome_options.add_argument('--headless')       # 无界面模式browser = webdriver.Chrome(chrome_options=chrome_options)browser.get('https://www.taobao.com')print(browser.current_url)print(browser.page_source)browser.close()

运行代码后不弹出Chrome浏览器并自动访问淘宝,然后在控制台输出淘宝的网址和页面的源代码,随后关闭浏览器。这几行简单的代码就实现了浏览器的驱动并获取网页源代码,非常便捷。

4、 查找节点Selenium还可以驱动浏览器完成各种操作,如填充表单、模拟点击等。比如要完成向某个输入框输入文字的操作,首先要找到输入框。Selenium有提供一系列查找节点的方法,可用这些方法获取想要的节点,以便下一步执行一些动作或者提取信息。

4.1、 提取单个节点例如要提取淘宝页面中的搜索框节点,需要先观察它的源代码。如图1-1所示。

图1-1 源代码

                                           图1-1   搜索框源代码

从源代码可以看到,搜索框节点的id是q,name也是q。另外还有许多其他属性,此时可用多种方式获取它。比如,find_element_by_name()是根据name值获取,find_element_by_id()是根据id获取。还有根据 XPath、CSS 选择器获取的方式。代码示例如下:

from selenium import webdriverbrowser = webdriver.Chrome()browser.get('https://www.taobao.com')input_first = browser.find_element_by_id('q')input_second = browser.find_element_by_css_selector('#q')input_third = browser.find_element_by_xpath('//*[@id="q"]')print(input_first, input_second, input_third)browser.close()

这里用了3种方式获取输入框,分别是根据ID、CSS选择器和XPath获取,3种方式返回的结果完全一致,并且都是WebElement类型。输出如下所示:

获取单个节点的方法有下面这些:find_element_by_idfind_element_by_namefind_element_by_xpathfind_element_by_link_textfind_element_by_partial_link_textfind_element_by_tag_namefind_element_by_class_namefind_element_by_css_selector

Selenium的通用方法 find_element(),需传入两个参数:查找方式 By和值。是find_element_by_id()方法的通用函数版本。如find_element_by_id(id)等价于find_element(By.ID, id),两种方法得到的结果是一样的。示例如下:

from selenium import webdriverfrom selenium.webdriver.common.by import Bybrowser = webdriver.Chrome()browser.get("https://www.taobao.com")input_first = browser.find_element(By.ID, 'q')print(input_first)browser.close()

这种查找方式的参数更灵活,功能与前面列举的是一样的。

4.2、 多个节点find_element() 方法只能查找单个节点,就算有多个节点,也只能得到第一个节点。节点类型是:WebElement。find_elements() 方法可以查找所有满足条件的节点。结果是列表类型,每个节点类型是:WebElement

例如查找淘宝左侧导航条的所有条目,通过源代码分析可知,每一个导航条都是用 li 标签包起来的,这些导航条都有一个共同的父标签ul,ul标签有class属性,其属性值是service-bd。可先根据class属性值找到ul标签,继而找到下面的子标签即可找到左侧导航条的所有节点。代码如下:

from selenium import webdriverbrowser = webdriver.Chrome()browser.get("https://www.taobao.com")lis = browser.find_elements_by_css_selector(".service-bd li")print(lis)browser.close()

输出如下所示:[, , , , , , , , , , , , , , , ]

输出结果是列表类型,列表中的每个节点都是WebElement类型。获取多个节点的所有方法如下:find_elements_by_idfind_elements_by_namefind_elements_by_xpathfind_elements_by_link_textfind_elements_by_partial_link_textfind_elements_by_tag_namefind_elements_by_class_namefind_elements_by_css_selector通用方法:find_elements()

使用通用方法find_elements()方法选择时,可这样写:lis = browser.find_elements(By.CSS_SELECTOR, '.service-bd li')

5、 节点交互Selenium可以让浏览器模拟执行一些动作。常见用法有:输入文字用 send_keys() 方法,清空文字用 clear() 方法,点击按钮用 click()方法。基本用法如下:

from selenium import webdriverimport timebrowser = webdriver.Chrome()browser.get("https://www.taobao.com")input = browser.find_element_by_id('q')    # 获取输入框input.send_keys("Mate20")                         # 输入Mate20time.sleep(1)input.clear()                                                # 等待1秒后清空输入框input.send_keys("P20")                              # 重新输入P20button = browser.find_element_by_class_name('btn-search')   # 获取搜索按钮button.click()     # 点击搜索

上面代码执行过程:首先驱动浏览器打开淘宝网站,然后用find_element_by_id()方法获取输入框,接着用send_keys()方法输入Mate20文字,等待1秒后用clear()方法清空输入框,再次调用send_keys()方法输入P20,之后再用find_element_by_class_name()方法获取搜索按钮,最后调用click()方法完成搜索动作。

官方文档的交互动作介绍:http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement

6、 动作链前面的交互动作是针对某个节点执行的。例如,对于输入框,可调用它的输入文字和清空文字方法;对于按钮,可调用它的点击方法。有一些操作,它们没有特定的执行对象,比如鼠标拖曳、键盘按键等,这些动作用另一种方式来执行,就是动作链。

例如要实现一个节点的拖曳操作,将某个节点从一处拖曳到另一处,可像下面这样实现:

from selenium import webdriverfrom selenium.webdriver import ActionChainsbrowser = webdriver.Chrome()url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'browser.get(url)browser.switch_to.frame('iframeResult')source = browser.find_element_by_css_selector('#draggable')target = browser.find_element_by_css_selector('#droppable')actions = ActionChains(browser)actions.drag_and_drop(source, target)actions.perform()

运行这段代码,首先打开网页一个拖曳实例,接着选中要拖曳的节点和拖曳到的目标节点,再接着声明ActionChains对象并将其赋值为actions变量,然后通过调用actions变量的drag_and_drop()方法,再调用perform()方法执行动作,此时就完成拖曳操作。

动作链接官方文档:http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.common.action_chains

7、 执行JavaScript某些操作,SeleniumAPI 没有提供。比如,下拉进度条操作就没有,它可以使用 execute_script() 方法直接模拟运行JavaScript来实现。示例如下:

from selenium import webdriverbrowser = webdriver.Chrome()browser.get('https://www.zhihu.com/explore')browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')browser.execute_script('alert("To Bottom")')

代码中利用execute_script()方法将进度条下拉到最底部,然后弹出alert提示框。有了这个方法,基本上API没有提供的所有功能都可以用执行JavaScript的方式来实现。

8、 获取节点信息page_source属性可获取网页源代码,解析库(有正则表达式、Beautiful Soup、pyquery等)用来提取信息。Selenium有提供节点选择方法,返回的是WebElement类型,对应也有相关的方法和属性直接提取节点信息,如属性、文本等。

8.1、 获取属性get_attribute()方法获取节点属性,需要先选中节点,代码示例如下:

from selenium import webdriverbrowser = webdriver.Chrome()url = 'https://www.zhihu.com/explore'browser.get(url)logo = browser.find_element_by_id('zh-top-link-logo')print(logo)print(logo.get_attribute('class'))

运行程序,驱动浏览器打开知乎页面,然后获取知乎的logo节点,最后打印出class。输出信息如下所示:zu-top-link-logo

这样通过get_attribute()方法传入属性名参数就可获取到属性值。

8.2、 获取文本值每个WebElement节点都有text属性,调用该属性可获取节点内部的文本信息。相当于Beautiful Soup的get_text()方法、pyquery的text()方法,示例如下:

from selenium import webdriverbrowser = webdriver.Chrome()         # 驱动打开浏览器url = 'https://www.zhihu.com/explore'browser.get(url)                                # 打开知乎页面input = browser.find_element_by_class_name('zu-top-add-question') # 获取提问节点print(input.text)                                #  输出:提问

8.3、 获取id、位置、标签名和大小WebElement节点的其它属性如下:id属性:获取节点idlocation属性:获取该节点在页面中的相对位置tag_name属性:获取标签名称size属性:获取节点的大小,也是宽高这几个属性在某些时候很有用的。

from selenium import webdriverbrowser = webdriver.Chrome()        # 驱动打开浏览器url = 'https://www.zhihu.com/explore'browser.get(url)                                # 打开知乎页面input = browser.find_element_by_class_name('zu-top-add-question') # 获取提问节点print(input.id)                                   # 获取节点idprint(input.location)                         # 节点在页面中的相对位置print(input.tag_name)                      # 标签名称print(input.size)                                # 标签的宽高

输出如下所示:0.46332722296830897-2{'x': 758, 'y': 7}button{'height': 32, 'width': 66}

9、 切换Frame网页中有一种节点叫作 iframe,也是子Frame,相当于页面的子页面,子页面结构与外部网页结构完全一致。Selenium打开页面默认是在父级Frame里面操作,页面中如果有子Frame,它是不能获取到子Frame里面的节点。这时可使用switch_to.frame()方法可切换frame。示例如下:

from selenium import webdriverfrom selenium.common.exceptions import NoSuchElementExceptionbrowser = webdriver.Chrome()url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'browser.get(url)browser.switch_to.frame('iframeResult') # 切换到子frametry: # 获取父级Frame的logo节点,不成功就抛出NoSuchElementException异常 logo = browser.find_element_by_class_name('logo')except NoSuchElementException: print('NO LOGO')browser.switch_to.parent_frame() # 切换回父级Framelogo = browser.find_element_by_class_name('logo') # 获取logo节点print(logo)print(logo.text) # 输出父级logo节点的文本

输出如下所示:NO LOGORUNOOB.COM

代码中switch_to.frame()方法切换到子Frame,接着find_element_by_class_name('logo')获取父级Frame的logo节点,未能获取就抛出异常NoSuchElementException。切换回父级Frame,重新获取logo节点,可以成功获取。如果页面中有子Frame时,要获取子Frame的节点,要先调用switch_to.frame()方法切换到对应的Frame后再进行操作。

10、 延时等待在Selenium中,get()方法在网页框架加载结束后结束执行,此时获取page_source,并不是浏览器完全加载完成的页面,如果有额外的Ajax请求,在网页源代码中也不一定能成功获取到。所以需要延时等待一定时间,确保节点已经加载出来。

延时等待有两种方式:隐式等待;显式等待。

10.1、 隐式等待,implicitly_wait()使用隐式等待测试时,如果Selenium没有在DOM中找到节点,将继续等待,超出设定时间后,就抛出找不到节点的异常。也就是说,在查找节点时节点没有立即出现时,隐式等待将等待一段时间再查找DOM,默认等待时间是0。示例如下:

from selenium import webdriverbrowser = webdriver.Chrome()browser.implicitly_wait(10)            # 调用隐式等待,等待10秒browser.get('https://www.zhihu.com/explore')input = browser.find_element_by_class_name('zu-top-add-question')print(input)

10.2、 显式等待,WebDriverWait()隐式等待方式会受到网络条件影响,有的页面加载时间过长。显式等待是指定要查找的节点,并指定一个最长等待时间。如果在规定时间内加载出来了这个节点,就返回查找的节点;到了规定时间依然没有加载出该节点,则抛出异常。示例如下:

from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECbrowser = webdriver.Chrome()browser.get('https://www.taobao.com/')wait = WebDriverWait(browser, 10)       # 参数:等待对象及时长# 在等待时间内获取输入框节点,通过ID查找input = wait.until(EC.presence_of_element_located((By.ID, 'q')))# 在等待时间内获取点击按钮节点,通过CSS选择器查找button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '.btn-search')))print(input, button)

在代码中引入WebDriverWait对象,指定最长等待时间为10秒,接着调用它的 until() 方法,传入要等待条件 expected_conditions。这里传入了 presence_of_element_located 这个条件,表示节点出现的意思,其参数是节点的定位元组,也就是ID为q的节点搜索框。在10秒内ID为q的节点(即搜索框)成功加载,就返回该节点;如果超过10秒还没有加载出来,就抛出异常。

按钮的等待条件是 element_to_be_clickable,也就是可点击。参数(By.CSS_SELECTOR, '.btn-search')意思是查找按钮时查找CSS选择器为 .btn-search 的按钮,如果10秒内它是可点击,就成功加载出来并返回这个按钮节点;如果10秒还不可点击,就是没有加载出来,则抛出异常。运行这段代码,在网速好的情况下可正常加载出来,并且输出如下:

从输出可知,输出了两个节点,都是WebElement类型。如果网络有问题就抛出异常。在这段代码用到了两个等待条件,这些等待条件还有很多,比如判断标题内容,判断某个节点内是否出现某文字等。表1-1是所有的等待条件。

表1-1 等待条件及其含义    image

更多等待条件参数及用法,参考官方文档:http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.support.expected_conditions

11、 前进和后退在使用浏览器时有前进和后退功能,Selenium 也可完成这个操作。使用 back() 方法后退,使用 forward() 方法前进。

import timefrom selenium import webdriverbrowser = webdriver.Chrome()browser.get('https://www.taobao.com/')     # 连续访问3个页面browser.get('https://www.baidu.com/')browser.get('https://www.sina.com.cn/')browser.back()                                            # 后退到百度页面time.sleep(1)browser.forward()                                       # 前进到sina页面time.sleep(3)browser.close()

这段代码连续访问3个页面后调用back()方法回到第二个页面,接下来调用forward()方法又前进到第三个页面。

12、 对Cookies的操作对Cookies进行操作方法主要有:获取、添加、删除等。get_cookies()方法:获取所有Cookies。add_cookie(字典参数):添加cookie,参数是字典。delete_all_cookies():删除所有的cookies。示例如下:

from selenium import webdriverbrowser = webdriver.Chrome()browser.get('https://www.zhihu.com/explore')print("第一次cookies:", browser.get_cookies())     # 获取cookies,接着下面添加cookiesbrowser.add_cookie({'name': 'name', 'domain': 'www.zhihu.com', 'value': 'michael'})print("第二次cookies:", browser.get_cookies())     # 再次获取cookies,核实是否添加成功browser.delete_all_cookies()                                   # 删除所有cookiesprint("第三次cookies:", browser.get_cookies())     # 核实是否完全删除cookiesbrowser.close()

输出如下,第二次输出的cookies包含了添加的cookie:第一次cookies: [{'domain': '.zhihu.com', 'httpOnly': False, ...}, ......]第二次cookies: [{'domain': '.zhihu.com', 'httpOnly': False, ...}, ......, {'domain': 'www.zhihu.com', 'name': 'name', 'value': 'michael'}]第三次cookies: []

13、 Selenium模拟开启选项卡比如第一个选项卡打开百度网页,第二个选项卡打开淘宝网页。这些操作也可用Selenium来对选项卡进行操作。window.open()是JavaScript语句的开启一个选项卡。execute_script('window.open()') 执行JavaScript语句开启一个选项卡。window_handles获取当前开启的选项卡,结果是选项卡代号列表。window_handles[0]是指选项卡列表中的第1个选项卡。switch_to.window(选项卡参数):切换到选项卡参数指定的选项卡。代码示例如下:

import timefrom selenium import webdriverbrowser = webdriver.Chrome()browser.get('https://www.baidu.com')browser.execute_script('window.open()')       # 开启一个新选项卡print(browser.window_handles)                     # 输出当前开启的选项卡browser.switch_to.window(browser.window_handles[1])      # 切换到新选项卡,也是第2个选项卡browser.get('https://www.taobao.com')       # 在第2个选项卡中打开淘宝页面time.sleep(1)browser.switch_to.window(browser.window_handles[0])    # 切换到第1个选项卡browser.get('https://www.sina.com.cn')        # 在第1个选项卡打开新浪页面browser.close()                                              # 关闭当前选项卡,也是第1个选项卡

输出如下所示:['CDwindow-AAC4839C9E18D601645AC4D868050F5D', 'CDwindow-37138AB8D7A2E7501E48014FB506977C']

14、 异常处理使用Selenium的时候,可能会遇到访问超时异常、节点未找到异常等情况。出现异常程序就中断运行。为了避免程序中断执行,可使用try except语句捕获各种异常。

使用Selenium时,常遇到的异常是:TimeoutException(超时异常),NoSuchElementException(节点未找到异常),此外还可用WebDriverException异常捕获所有由Selenium产生的异常。异常模块所在位置是:selenium.common.exceptions。

导入WebDriverException的命令:from selenium.common.exceptions import WebDriverException

Selenium的异常类官方文档参考:http://selenium-python.readthedocs.io/api.html#module-selenium.common-exceptions



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有